/*******************************************************************************
 * Copyright © 2012 Ben Swim
 *
 * This file is part of ImoSPC.
 *
 * ImoSPC is free software: you can redistribute it and/or modify it under the
 * terms of the GNU General Public License as published by the Free Software
 * Foundation, either version 3 of the License, or (at your option) any later
 * version.
 *
 * ImoSPC is distributed in the hope that it will be useful, but WITHOUT ANY
 * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
 * A PARTICULAR PURPOSE. See the GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License along with
 * ImoSPC. If not, see <http://www.gnu.org/licenses/>.
 *******************************************************************************/
package net.smwcentral;

// Represents a cache entry.
class CacheEntry {
	public final String url;
	
	public final byte[] last;
	public final FixedMap<String, byte[]> entries;
	
	public CacheEntry(String url, byte[] last, FixedMap<String, byte[]> entries){
		this.url = url;
		this.last = last;
		this.entries = entries;
	}
}

// Represents a cache that acts as a queue, but also has keys.
class KeyedCache {
	private final Object m_lock = new Object();
	
	public final int cacheSize;
	
	private final int[] m_hashKeys;
	private final CacheEntry[] m_entries;
	
	private int m_count;
	
	public KeyedCache(int cacheSize) {
		if (cacheSize < 0) throw new IllegalArgumentException("Size of cache" +
												" must be greater than or " +
												"equal to zero.");
		this.cacheSize = cacheSize;
		
		// Initialize the slots
		m_hashKeys = new int[cacheSize];
		m_entries = new CacheEntry[cacheSize];
		// No entries!
		m_count = 0;
	}
	
	// Get an entry directly by its index.
	public CacheEntry get(int index) {
		if (cacheSize <= 0) return null;
		
		if (index >= m_count ||
			index >= cacheSize) return null;
		
		synchronized(m_lock) {
			if (index >= m_count ||
				index >= cacheSize) return null;
			return m_entries[index];
		}
	}
	
	// Look up an entry by the URL.
	public CacheEntry get(String url) {
		if (url == null) return null;
		if (cacheSize <= 0) return null;
		if (m_count <= 0) return null;
		
		synchronized(m_lock) {
			if (m_count <= 0) return null;

			int hash = url.hashCode();
			for (int i = 0; i < m_count; i++) {
				if (hash != m_hashKeys[i]) continue;

				CacheEntry entry = m_entries[i];
				if (!url.equals(entry.url)) continue;

				// Shift this entry to the top of the cache.
				final int last = m_count - 1;
				if (i < last) {
					for (int j = i; j < last; j++) {
						m_hashKeys[j] = m_hashKeys[j + 1];
						m_entries[j] = m_entries[j + 1];
					}
					m_hashKeys[last] = hash;
					m_entries[last] = entry;
				}
				return entry;
			}
		}
		return null;
	}
	
	// Gets the most recent addition to the cache.
	public CacheEntry newest() {
		if (m_count <= 0) return null;
		
		synchronized(m_lock) {
			if (m_count <= 0) return null;
			return m_entries[m_count - 1];
		}
	}
	
	// Puts a new entry into the cache, automatically removing the oldest entry,
	// if necessary.
	public CacheEntry put(CacheEntry entry) {
		if (cacheSize <= 0) return entry;
		
		synchronized(m_lock) {
			if (m_count >= cacheSize - 1) {
				// Slide an item off of the bottom of the cache.
				m_count = cacheSize - 1;
				// Push those entries downward!
				for (int i = 0; i < m_count; i++) {
					m_hashKeys[i] = m_hashKeys[i + 1];
					m_entries[i] = m_entries[i + 1];
				}
			}
			
			// Push the entry onto the top of the cache.
			m_hashKeys[m_count] = entry.url.hashCode();
			m_entries[m_count] = entry;
			// New item!
			m_count++;
		}
		
		return entry;
	}
	
	// Number of cached items
	public int getCount() { return m_count; }
	
	// Clear this cache
	public void clear() {
		synchronized(m_lock) {
			m_count = 0;
			for (int i = 0; i < cacheSize; i++) {
				m_entries[i] = null;
				m_hashKeys[i] = 0;
			}
		}
	}
}
